﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace SimpleMVC.Models
{

    [StudnetModelBinder]
    public class Student:IValidatableObject
    {
        public Student():this(String.Empty, String.Empty, 20)
        {
            
        }
        public Student(string name, string studentNo,int age)
        {
            Name = name;
            Age = age;
            StudentNo = studentNo;
        }
        
        public string Name { get; }

        public string StudentNo { get; }


        [Range(10,100)]
        public int Age { get; }

        [Range(0, 100)]
        public double? Score { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (string.IsNullOrEmpty(Name) && string.IsNullOrEmpty(StudentNo))
            {
                yield return new ValidationResult("Name or StudentNo is required");
            }
        }
    }

    public class StudnetModelBinderAttribute : CustomModelBinderAttribute
    {
        private readonly StudentModelBinder _studentModelBinder = new StudentModelBinder();
        public override IModelBinder GetBinder()
        {
            return _studentModelBinder;
        }
    }

    public class StudentModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (!string.IsNullOrEmpty(bindingContext.ModelName) &&
                !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))
            {
                if (!bindingContext.FallbackToEmptyPrefix)
                    return null;
                bindingContext = new ModelBindingContext()
                {
                    ModelMetadata = bindingContext.ModelMetadata,
                    ModelState = bindingContext.ModelState,
                    PropertyFilter = bindingContext.PropertyFilter,
                    ValueProvider = bindingContext.ValueProvider
                };
            }
            var stu = new Student(GetValue<string>(controllerContext, bindingContext,"Name"),
                GetValue<string>(controllerContext, bindingContext, "StudentNo"),
                GetValue<int>(controllerContext, bindingContext, "Age"));
            stu.Score = GetValue<int?>(controllerContext, bindingContext, "Score");
            ValidateModel(controllerContext, bindingContext, stu);
            return stu;
        }
        

        private TModel GetValue<TModel>(ControllerContext controllerContext, ModelBindingContext bindingContext, string name)
        {
            string fullName = name;
            if (!string.IsNullOrEmpty(bindingContext.ModelName))
                fullName = bindingContext.ModelName + "." + name;
            ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(fullName);
            ModelState modelState = new ModelState() {Value = valueResult };
            bindingContext.ModelState.Add(fullName, modelState);
            ModelMetadata metadata = bindingContext.PropertyMetadata[name];
            TModel model;
            if (metadata != null)
            {
                model = GetValueAndValidate<TModel>(controllerContext, bindingContext, valueResult, metadata, modelState);
            }
            else
            {
                try
                {
                    model = (TModel) valueResult.ConvertTo(typeof (TModel));
                }
                catch (Exception ex)
                {
                    model = default(TModel);
                    modelState.Errors.Add(string.Format("the value {0} is not a valid value for {1}", valueResult.AttemptedValue, name));
                }
            }
            return model;
        }

        private TModel GetValueAndValidate<TModel>(ControllerContext controllerContext,
            ModelBindingContext bindingContext, ValueProviderResult valueResult, ModelMetadata metadata, ModelState modelState)
        {
            TModel model;
            string attemptedValue = valueResult.AttemptedValue;
            bool invalidValue = false;
            if (metadata.ConvertEmptyStringToNull && string.IsNullOrWhiteSpace(attemptedValue))
                attemptedValue = null;
            try
            {
                model = (TModel) valueResult.ConvertTo(typeof (TModel));
                metadata.Model = model;
            }
            catch (Exception ex)
            {
                model = default(TModel);
                metadata.Model = attemptedValue;
                invalidValue = true;
            }
            IEnumerable<ModelValidator> validators = ModelValidatorProviders.Providers.GetValidators(metadata,
                controllerContext);
            foreach (var validator in validators)
            {
                foreach (var validatorResult in validator.Validate(bindingContext.Model))
                {
                    modelState.Errors.Add(validatorResult.Message);
                }
            }
            if (invalidValue && modelState.Errors.Count == 0)
            {
                modelState.Errors.Add(string.Format("the value {0} is not a valid value for {1}", attemptedValue,
                    metadata.GetDisplayName()));
            }
            return model;
        }

        private void ValidateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object stu)
        {
            ModelMetadata metadata = bindingContext.ModelMetadata;
            metadata.Model = stu;
            IEnumerable<ModelValidator> validators = ModelValidatorProviders.Providers.GetValidators(metadata,
                controllerContext);
            foreach (var validator in validators)
            {
                foreach (var validatorResult in validator.Validate(null))
                {
                    var msg = validatorResult.Message;
                    bindingContext.ModelState.AddModelError("Student", msg);
                }
            }
        }
    }
}